package io.cici.cc.mybatis.lite.reflection;

import io.cici.cc.mybatis.lite.annotations.Parameter;
import io.cici.cc.mybatis.lite.executor.resultset.ResultContextHandler;
import io.cici.cc.mybatis.lite.session.Configuration;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * 参数分解
 *
 * @author huihui
 */
public class ParameterResolver {

    public static final String GENERIC_NAME_PREFIX = "param";

    private final boolean useActualParamName;


    private final SortedMap<Integer, String> parameterIndexToParameterMap;

    private boolean hasParameterAnnotation;

    public ParameterResolver(Configuration configuration, Method method) {
        this.useActualParamName = configuration.isUseActualParamName();
        final SortedMap<Integer, String> map = new TreeMap<>();
        final Class<?>[] parameterTypes = method.getParameterTypes();
        final Annotation[][] parameterAnnotations = method.getParameterAnnotations();

        int parameterAnnotationsCount = parameterAnnotations.length;

        for (int i = 0; i < parameterAnnotationsCount; i++) {
            if (isResultContextHandler(parameterTypes[i])) {
                continue;
            }
            String parameterName = null;
            boolean hasParameterAnnotation = false;
            for (Annotation annotation : parameterAnnotations[i]) {
                if (annotation instanceof Parameter) {
                    if (hasParameterAnnotation) {
                        throw new IllegalArgumentException("一个参数只能配置参数注解");
                    } else {
                        hasParameterAnnotation = true;
                        parameterName = ((Parameter) annotation).value();
                    }
                }
            }
            if (parameterName == null) {
                throw new IllegalArgumentException("请配置参数名");
            }
            map.put(i, parameterName);
        }
        if (!map.isEmpty()) {
            hasParameterAnnotation = true;
        }
        parameterIndexToParameterMap = Collections.unmodifiableSortedMap(map);
    }

    private static boolean isResultContextHandler(Class<?> clazz) {
        return ResultContextHandler.class.isAssignableFrom(clazz);
    }


    public static Object wrapToMapIfCollection(Object object, String actualParamName) {
        if (object instanceof Collection) {
            ParameterMap<Object> map = new ParameterMap<>();
            map.put("collection", object);
            if (object instanceof List) {
                map.put("list", object);
            }
            Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
            return map;
        }
        if (object != null && object.getClass().isArray()) {
            ParameterMap<Object> map = new ParameterMap<>();
            map.put("array", object);
            Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
            return map;
        }
        return object;
    }


    public String[] getParameterIndexToParameterMap() {
        return parameterIndexToParameterMap.values().toArray(new String[0]);
    }


    public Object getNamedParams(Object[] args) {
        final int paramCount = parameterIndexToParameterMap.size();
        if (args == null || paramCount == 0) {
            return null;
        }
        if (!hasParameterAnnotation && paramCount == 1) {
            Object value = args[parameterIndexToParameterMap.firstKey()];
            return wrapToMapIfCollection(value, useActualParamName ? parameterIndexToParameterMap.get(parameterIndexToParameterMap.firstKey()) : null);
        } else {
            final Map<String, Object> param = new ParameterMap<>();
            int i = 0;
            for (Map.Entry<Integer, String> entry : parameterIndexToParameterMap.entrySet()) {
                param.put(entry.getValue(), args[entry.getKey()]);
                // add generic param names (param1, param2, ...)
                final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
                // ensure not to overwrite parameter named with @Param
                if (!parameterIndexToParameterMap.containsValue(genericParamName)) {
                    param.put(genericParamName, args[entry.getKey()]);
                }
                i++;
            }
            return param;
        }
    }
}
